package apis

import (
	"config"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"math"
	"net/http"
	"net/url"
	"regexp"
	"spirit/mongodb"
	"spirit/redis"
	"spirit/utils/common"
	"spirit/utils/net"
	"strings"
	"sync"
	"time"
)

const (
	TOKEN_TIMEOUT       = 7200
	TOKEN_LIMIT_TIMES   = 1000
	PREStr              = "Screening"
	GETDATA_LIMIT_TIMES = 1000 //获取数据次数
	QUERY_LIMIT         = 100
)

const (
	REDISDB           = "screening"
	CODE_TOKEN_EXPIRE = 1000 //token过期
	MSG_TOKEN_EXPIRE  = "token已过期"
	CODE_E1           = 1001
	MSG_E1            = "token错误"
	CODE_E2           = 1002
	MSG_E2            = "参数错误"
	CODE_E3           = 1003
	MSG_E3            = "调用次数过超限制"
	CODE_E4           = 1004
	MSG_E4            = "服务过期"
	CODE_E5           = 1005
	MSG_E5            = "服务条数过超限制"
	CODE_E6           = 1006
	MSG_E6            = "签名过期"
	CODE_E7           = 1007
	MSG_E7            = "签名错误"
	CODE_E8           = 1008
	MSG_E8            = "服务条数过超月限额"
	CODE_ERR_E0       = 4000
	MSG_ERR_E         = "内部错误"
	CODE_ERR_E1       = 4001
	CODE_ERR_E2       = 4002
	CODE_ERR_E3       = 4003
	CODE_ERR_E4       = 4004
	//------
	//	CODE_OUTTIME      = 0
	//	MSG_OUTTIME       = "不在服务时间(服务时间06:00-23:00)"
	CODE_SUCCESS = 1
	MSG_SUCCESS  = "请求成功"
)

var (
	EncodeUtil = &SimpleEncrypt{"screening2020"}
)

type ApiController struct{}

func (api *ApiController) Router(router *gin.Engine) {

	router.POST("/apis/newUser", func(context *gin.Context) {
		newUser(context.Writer, context.Request)
	})
	router.POST("/user/access_token", func(context *gin.Context) {
		getAccessToken(context.Writer, context.Request)
	})

	router.POST("/data/getScreeningData", func(context *gin.Context) {
		getScreeningData(context.Writer, context.Request)
	})

	router.POST("/data/saveScreeningData", func(context *gin.Context) {
		saveScreeningData(context.Writer, context.Request)
	})

}

func saveScreeningData(writer gin.ResponseWriter, request *http.Request) {
	access_token := request.FormValue("access_token")
	b, appid, apiUserName, apiUserCode, area, d := CheckUserInfo(access_token)
	if b && appid != "" && apiUserName != "" && apiUserCode != "" && len(d) == 0 {
		data := net.RedingJsonDataFromRequestBody(request)
		m := map[string]interface{}{}
		if data != "" {
			if err := json.Unmarshal([]byte(data), &m); err != nil {
				log.Println("接口收到数据反序列化出错:", err)
				d["code"] = CODE_E2
				d["msg"] = MSG_E2
			} else {
				m["appid"] = appid
				m["createOrganName"] = apiUserName
				m["createOrganCode"] = apiUserCode
				m["area"] = area
				m["infoType"] = "api"
				m["updateTime"] = time.Now().Unix()
				dealData(&m)
				mongodb.Save(config.TbBaseName2, m)
			}
		}
	}
	WriteJSON(writer, d)
}

func getScreeningData(w http.ResponseWriter, request *http.Request) {
	access_token := request.FormValue("access_token")
	b, appid, _, _, _, d := CheckUserInfo(access_token)
	if b && appid != "" {
		limittodaykey := fmt.Sprintf("limittoday_%d_%s", time.Now().Day(), appid)
		redis.Incr(REDISDB, limittodaykey)
		year := request.FormValue("year")
		idCard := request.FormValue("idCard")
		userName := request.FormValue("userName")
		if m := getUserYearScreenData(idCard, userName, year); m != nil {
			d["data"] = *m
			d["code"] = 0
			d["msg"] = "success"
		}
	}
	WriteJSON(w, d)
}

func getAccessToken(w http.ResponseWriter, r *http.Request) {
	defer common.Catch()
	d := map[string]interface{}{}
	appid := r.FormValue("appid")
	key := r.FormValue("key")
	signature := r.FormValue("signature")
	timestamp := r.FormValue("timestamp")
	//userType := 0
	PRE := ""
	if signature != "" && timestamp != "" {
		PRE = PREStr
	} else {
		d["msg"] = MSG_E2
		d["code"] = CODE_E2
		WriteJSON(w, d)
		return
	}
	if appid != "" {
		var res *map[string]interface{}
		q := map[string]interface{}{
			"appid": appid,
		}
		res = mongodb.FindOneByField("api_user", &q, `{"name":1,"code":1,"area":1,key":1,"bnormal":1}`)
		if res != nil && *res != nil {
			bcon := false
			t1 := Int64All(timestamp)
			t2 := time.Now().Unix()
			if math.Abs(float64(t2-t1)) > 600 {
				d["code"] = CODE_E6
				d["msg"] = MSG_E6
			} else {
				keys, _ := (*res)["key"].(string)
				h := md5.New()
				h.Write([]byte(appid + timestamp + keys))
				s1 := strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
				if s1 == signature {
					bcon = true
					if key == "" {
						key = keys
					}
				} else {
					d["code"] = CODE_E7
					d["msg"] = MSG_E7
				}
			}

			if bcon {
				GetDataMapLock.Lock()
				appidLock := GetDataMap[appid]
				if appidLock == nil {
					appidLock = &sync.Mutex{}
					GetDataMap[appid] = appidLock
				}
				GetDataMapLock.Unlock()
				appidLock.Lock() //取锁
				defer appidLock.Unlock()
				t := time.Now()
				limitKey := fmt.Sprintf("tokenlimit_%d_%s", t.Day(), appid)
				limit := redis.GetInt(REDISDB, limitKey)
				if limit < TOKEN_LIMIT_TIMES {
					apiUser, _ := (*res)["name"].(string)
					apiUserCode, _ := (*res)["code"].(string)
					apiUserArea, _ := (*res)["area"].(string)
					if apiUser != "" {
						access_token := RsaEncrypt(fmt.Sprintf("%d,%s,%s,%s,%s,%s", t.Unix(), appid, key, apiUser, apiUserCode, apiUserArea))
						log.Printf("appid :[%s] key:[%s] ,生成 access_token:[%s]", appid, key, access_token)
						if access_token != "" {
							if redis.Put(REDISDB, "token_"+appid, access_token, TOKEN_TIMEOUT) {
								d["access_token"] = PRE + url.QueryEscape(access_token)
								if limit == 0 {
									tn := time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, time.Local).Unix()
									expire := int(tn - t.Unix())
									redis.Put(REDISDB, limitKey, 1, expire)
								} else {
									redis.Incr(REDISDB, limitKey)
								}
								d["expires_in"] = TOKEN_TIMEOUT
								d["available_times"] = TOKEN_LIMIT_TIMES - limit - 1
							}
						}
					}
					if len(d) == 0 {
						//服务出错
						d["code"] = CODE_ERR_E0
						d["msg"] = MSG_ERR_E
						log.Println(d)
						d["code"] = CODE_E2
						d["msg"] = MSG_E2
					}
				} else {
					//超过次数
					d["code"] = CODE_E3
					d["msg"] = MSG_E3
				}
			}
		} else {
			log.Println("查询用户信息为空", appid, key)
		}
		go func() {
			//保存日志
			at := common.IntAll(d["available_times"])
			saveData := map[string]interface{}{
				"appid":           appid,
				"available_times": at,
				"flag":            "token",
				"date":            time.Now().Unix(),
			}
			if mongodb.Save("apilog", saveData) == "" {
				//log.Println(saveData)
			}
		}()
	} else {
		log.Println("参数获取为空", appid, key)
	}
	if len(d) == 0 {
		d["code"] = CODE_E2
		d["msg"] = MSG_E2
	}
	WriteJSON(w, d)
}

func newUser(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	code := r.FormValue("code")
	area := r.FormValue("area")
	t := time.Now()
	//	randomstr := util.GetLetterRandom(5)
	//	str := fmt.Sprintf("%s%d%s", randomstr[:2], t.Unix(), randomstr[2:])
	//	appid := "jy" + EncodeUtil.EncodeString(str)
	appid := getAppid(t.Unix())
	key := GetComplexRandom(8, 3, 5)
	id := mongodb.Save("api_user", &map[string]interface{}{"area": area, "name": name, "code": code, "createtime": t.Unix(), "appid": appid, "key": key, "bnormal": int32(1)})
	log.Print("create user success!", "\t", name)
	ret := map[string]string{"msg": "err"}
	if len(id) > 10 {
		ret["msg"] = "ok"
	}
	WriteJSON(w, &ret)
}

var strReg = regexp.MustCompile("^[0-9a-zA-Z]+$")

func getAppid(tn int64) (appid string) {
	for {
		randomstr := GetLetterRandom(5)
		str := fmt.Sprintf("%s%d%s", randomstr[:2], tn, randomstr[2:])
		appid = EncodeUtil.EncodeString(str)
		if strReg.MatchString(appid) {
			break
		}
	}
	appid = "jy" + appid
	return
}

func CheckUserInfo(access_token string) (bcheck bool, appid, apiUser, apiUserCode, area string, d map[string]interface{}) {
	defer common.Catch()
	d = make(map[string]interface{}, 0)
	//第一层判断token是否失效或格式不对
	apiUser = ""
	if access_token != "" {
		if strings.HasPrefix(access_token, PREStr) {
			access_token = access_token[9:]
		}
		at := RsaDecrypt(access_token)
		if at == "" {
			access_token, _ = url.QueryUnescape(access_token)
			at = RsaDecrypt(access_token)
		}
		//log.Println("校验解析token:", at)
		if at != "" {
			tn := time.Now().Unix()
			arr := strings.Split(at, ",")
			if len(arr) == 6 { //时间,appid,key,user,code,area
				t := Int64All(arr[0])
				des := tn - t
				//log.Println("des", des)
				if des >= 0 && des <= TOKEN_TIMEOUT { //在有效时间内
					appid = arr[1]
					redis_token := redis.GetStr(REDISDB, "token_"+appid)
					//log.Println("redis_token", "\t", redis_token, "\t", access_token)
					if redis_token != "" && redis_token == access_token { //token验证通过，验证今日次数、总条数、服务时间
						bcheck = true
						apiUser = arr[3]
						apiUserCode = arr[4]
						area = arr[5]
					} else {
						d["code"] = CODE_TOKEN_EXPIRE
						d["msg"] = MSG_TOKEN_EXPIRE
					}
				} else {
					d["code"] = CODE_TOKEN_EXPIRE
					d["msg"] = MSG_TOKEN_EXPIRE
				}
			}
		}
	}
	if !bcheck && len(d) == 0 {
		d["code"] = CODE_E1
		d["msg"] = MSG_E1
	}
	//第二层判断用户调用权限
	if bcheck {
		bcheck = false
		tn := time.Now()
		tnUnix := tn.Unix()
		GetDataMapLock.Lock()
		appidLock := GetDataMap[appid]
		if appidLock == nil {
			appidLock = &sync.Mutex{}
			GetDataMap[appid] = appidLock
		}
		GetDataMapLock.Unlock()
		appidLock.Lock()
		defer appidLock.Unlock()
		limittodaykey := fmt.Sprintf("limittoday_%d_%s", tn.Day(), appid)
		limittoday := redis.GetInt(REDISDB, limittodaykey)
		if limittoday > GETDATA_LIMIT_TIMES { //当天调用超过次数
			d["code"] = CODE_E3
			d["msg"] = MSG_E3
		} else {
			if limittoday == 0 {
				_, max := GetDayMinMax(tn)
				redis.Put(REDISDB, limittodaykey, 0, int(max-tnUnix))
			}
			bcheck = true
		}
	}
	return
}

func getUserYearScreenData(idCard, userName, year string) *map[string]interface{} {
	query := make(map[string]interface{}, 0)
	if userName != "" {
		query["baseInfo.userName"] = userName
	}
	if idCard != "" {
		query["baseInfo.idCard"] = idCard
	}
	if year != "" {
		/*queryTbNames := make([]string, 0)
		queryTbNames = append(queryTbNames, config.CurrentYear)
		for _, year := range config.ScreenYearRecord {
			queryTbNames = append(queryTbNames, year)
		}*/

		tb1 := "infoCollection_" + year
		tb2 := "infoCollection2_" + year
		m := mongodb.FindOne(tb1, query)
		if m == nil || (*m) == nil {
			m = mongodb.FindOne(tb2, query)
		}
		return m
	}
	return nil
}
